AES対応のPython暗号化ライブラリを比較検証してみた
こんにちは。DA事業本部の春田です。
PythonでAES方式の暗号化を実現したかったため、暗号化系のライブラリが複数あったので比較検証してみました。
さっそくですが、「Python 暗号化」でググるとよくヒットするpycryptoというライブラリは、今回対象外としました。pycryptoライブラリはバグの報告が上がっているものの2013年10月以降アップデートが止まっているので、そもそもの使用を控えることにします。
今回比較するのは、PyCryptodomeとpyca/cryptographyです。
PyCryptodome
PyCryptodomeはLow-levelな暗号化機能が使えるPythonライブラリです。Python 2.6, 2.7, 3.4+に対応しています。先のPyCryptoからフォークしたライブラリで、後方互換性もある程度意識されているみたいですが、主要クラスのメソッドで削除されたものもあります。(詳しくはこちら→Compatibility with PyCrypto — PyCryptodome 3.9.7 documentation)
共通鍵暗号から、公開鍵暗号、認証付き暗号、暗号学的ハッシュなど、非常に多くの方式に対応しています。各種サブパッケージ をimportし、インスタンスとメソッドで実装を進めていく形となります。にしても、ドキュメントが整っていてかなり見やすい。
軽くAESを試してみました。インストールはおなじみのpipから可能です。
pip install pycryptodome
AESと乱数生成用のパッケージをimportします。
>>> from Crypto.Cipher import AES >>> from Crypto.Random import get_random_bytes >>> key = get_random_bytes(16) >>> key b"\x0f0oj'\xfc\x14h\xf0L\x99\xd9\xaf=\xdd\xfb" >>> cipher = AES.new(key, AES.MODE_EAX)
暗号化したいTarget data
という文字列を引数に、encrypt_and_digest
メソッドを使用すると、暗号化済みのテキストとMAC tagが返されます。
>>> ciphertext, tag = cipher.encrypt_and_digest(b'Target data') >>> ciphertext b'\xf8lf\xfa\xc2P#\x96\x12\x91\xc1' >>> tag b'\x889\xc9\xf0\xcd\xb9j-\xe1\xab\xbf\x0cF?L\xa1' >>> cipher.nonce b'~\x07\xc8l\xb6p}MWa\x9e{\xb4\x13"\x14'
復号する時はナンスを固定してインスタンスを作成し、decrypt_and_verify
メソッドに暗号化済みのテキストとtagを渡せばOKです。
>>> cipher_dec = AES.new(key, AES.MODE_EAX, cipher.nonce) >>> data = cipher_dec.decrypt_and_verify(ciphertext, tag) >>> data b'Target data'
Low-levelとの記載がありましたが、この時点でもかなりお手軽に使えました。
pyca/cryptography
一方、pyca/cryptographyの方は、Low-levelとHigh-levelのインターフェイスを持った暗号化ライブラリです。Python 2.7, 3.5+に対応しています。こちらも共通鍵暗号からキー生成といった機能は一通り揃っています。
強調しているのは、「暗号化についてよくわからないのであれば、High-levelの方を使ってね」ということです。Low-levelのインターフェイスは誤って使用するとセキュリティ的なリスクが大きいため、hazardous material、略してhazmat層という名前で定義されています。
pyca/cryptographyで提供している共通鍵暗号では、Fernetを採用しています。Fernetは、キー以外で暗号化の際に加えるべき情報(バージョンやタイムスタンプなど)を定めた規格で、暗号化はCBCモードのAES128で実施されます。
こちらも軽く試してみました。インストールはpipから可能です。
pip install cryptography
Fernetをimportすれば、キー生成と暗号化が行えます。encrypt
メソッドを実行するとトークンが返されます。
>>> from cryptography.fernet import Fernet >>> key = Fernet.generate_key() >>> key b'RguEWlPsq-A9MxQLb9FbFePV16d_DtTa0o4HKklhPvc=' >>> f = Fernet(key) >>> token = f.encrypt(b"Target data") >>> token b'gAAAAABe3y1OEs7h9_uuvBUau6BHSa_yef_5Ac0v75yZ2R_FZpEyc-7GJG2nOlZRngINF74dMqLnMG3MEPO0pHsBLqKDQc0MCw=='
復号はdecrypt
メソッドにトークンを渡すだけ。
>>> f.decrypt(token) b'Target data'
なるほど、PyCryptodomeよりpyca/cryptographyの方が幾分かシンプルですね。ただし、pyca/cryptographyが採用しているFernetのCBCモードのAES12について、PyCryptodomeのドキュメントに以下の記載がありました。
Classic modes of operation such as CBC only provide guarantees over the confidentiality of the message but not over its integrity. In other words, they don’t allow the receiver to establish if the ciphertext was modified in transit or if it really originates from a certain source.
CBCはconfidentiality(機密性)は保たれるけど、integrity(完全性)は保たれないらしいです。PyCryptodomeではCBCはクラシックと定義しているので、できればモダンなモードを採択した方がベター。
2020年6月現在で、PyCryptodomeでモダンなモードとして提供されているのは以下の5種類です。この辺りは連携するアプリでの対応状況やセキュリティ要件、暗復号の速度を加味して採択する必要があります。EASのモードの種類として、次のサイトが参考になりました。→How to choose an Authenticated Encryption mode – A Few Thoughts on Cryptographic Engineering
- CCM (Counter Mode with CBC MAC)
- EAX
- GCM (Galois Counter Mode)
- SIV (Synthetic Initialization Vector)
- OCB (Offset CodeBook mode)
pyca/cryptographyにもLow-levelのインターフェイスはありますが、設計的にPyCryptodomeよりも考慮すべきことが多そうでした。GCMモードのAESを安全に使うために、今回のライブラリ選定ではPyCryptodomeを使うことにします。
まとめと比較表
2020年6月現在での、両ライブラリの共通鍵を比較した表を作成しました。ご参考になれば幸いです。
アルゴリズム | PyCryptodome | pyca/cryptography |
---|---|---|
AES | ○ | ○ |
ARC4 | ○ | ○ |
Blowfish | ○ | ○ |
Camellia | ○ | |
CAST-128 | ○ | |
CAST-5 | ○ | |
ChaCha20 | ○ | ○ |
IDEA | ○ | |
PKCS#1 v1.5 encryption (RSA) | ○ | |
RC2 | ○ | |
Salsa20 | ○ | |
SEED | ○ | |
Single DES | ○ | |
Triple DES | ○ | ○ |
モード | PyCryptodome | pyca/cryptography |
---|---|---|
CBC | ○ | ○ |
CCM | ○ | |
CFB | ○ | ○ |
CFB8 | ○ | |
CTR | ○ | ○ |
EAX | ○ | |
ECB | ○ | ○ |
GCM | ○ | ○ |
OCB | ○ | |
OFB | ○ | ○ |
OpenPGP | ○ | |
SIV | ○ | |
XTS | ○ |
今回はAWS Lambda上でも使いたいので、ライブラリのサイズも合わせて調べてみました。どちらもサイズ的には問題ありません。
解凍済み | Zip | |
---|---|---|
Lambda Layerの上限 | 250 MB | 50 MB |
PyCryptodome 3.9.7 | 39.5 MB | 14.1 MB |
pyca/cryptography 2.9.2 | 8.4 MB | 2.6 MB |
参照
- Welcome to PyCryptodome’s documentation — PyCryptodome 3.9.7 documentation
- Welcome to pyca/cryptography — Cryptography 3.0.dev1 documentation
- GitHub - fernet/spec: Spec and acceptance tests for the Fernet format.
- How to choose an Authenticated Encryption mode – A Few Thoughts on Cryptographic Engineering
- 情報セキュリティの3要素とは
- AWS Lambda の制限 - AWS Lambda